/*
 * ScriptHost.cpp
 *
 *  Created on: Oct 27, 2013
 *      Author: blackpc
 */

#include <scriptable_analyzer/ScriptHost.h>

ScriptHost::ScriptHost()
{
	_executionInterval = 0.1;
	RosTopicListener::start();
}

ScriptHost::~ScriptHost()
{
	RosTopicListener::stop();
}

void ScriptHost::addScript(string sourceCode)
{
	lock(_scriptsMutex);

	set<string> internalFunctions;
	foreach (string functionName, InternalFunctionsManager::getFunctionNames()) {
		internalFunctions.insert(functionName);
	}

	/**
	 * Create predicate script from the source code
	 */
	PredicatesScript predicateScript(sourceCode, internalFunctions);
	if (predicateScript.getName() == "") {
		cerr << "[e] Unnamed script provided, cannot add to ScriptHost" << endl;
		return;
	}

	/**
	 * Convert to python script, simulate execution to extract used topic names
	 */
	PythonScript* pythonScript = new PythonScript(predicateScript.getPythonScript());
	bool validScript = prepareScript(*pythonScript);

	if (validScript)
		_scripts.push_back(boost::shared_ptr<PythonScript>(pythonScript));
	else {
		cerr << "[e] Invalid script" << endl;
		return;
	}

	/**
	 * Add used topics to listener
	 */
	foreach (string topicName, pythonScript->getUsedTopics()) {
		RosTopicListener::addTopic(topicName);
	}

	cout << "[+] Script added [" << pythonScript->getName() << "]" << endl;
}

bool ScriptHost::prepareScript(PythonScript& script)
{
	CompilationResult result = _executer.compile(script);
	if (!result.success) {
		cerr << "[e] Compilation of script '" << script.getName() << "' failed" << endl;
		cerr << "[e] Message: " << result.message << endl;
		return false;
	}

	/**
	 * Extracts used topics, validations, internal function etc...
	 */
	_executer.simulate(script);

	return true;
}

void ScriptHost::run()
{
	while (!_workThread->interruption_requested()) {

		{
			lock(_scriptsMutex);
			foreach (PythonScriptPtr script, _scripts) {
				if (isExecutionTime(script)) {
					cout << "[i] Time to execute '" << script->getName() << "'" << endl;
					_executer.execute(*script, RosTopicListener::getTopicsValues());
					script->updateExecutionTime();

					cout << "[i] Validations: " << (script->isValidationFailed() ? "FAILED [" + script->getFailedValidation() + "]" : "PASSED") << endl;

					addDiagnosticStatus(script);
				}
			}
		}

		boost::this_thread::sleep(boost::posix_time::milliseconds(1000.0 * _executionInterval));
	}
}

void ScriptHost::start()
{
	RosTopicListener::start();

	if (!_workThread)
		_workThread = boost::shared_ptr<boost::thread>(
				new boost::thread(boost::bind(&ScriptHost::run, this)));
}

void ScriptHost::stop()
{
	_workThread->interrupt();
	_workThread->join();
}

bool ScriptHost::isExecutionTime(PythonScriptPtr script)
{
	boost::posix_time::ptime nowTime = boost::posix_time::microsec_clock::local_time();
	boost::posix_time::ptime nextExecutionTime
		= script->getExecutionTime() + boost::posix_time::time_duration(boost::posix_time::milliseconds(1000.0 * script->getInterval()));

	return nowTime > nextExecutionTime;
}

void ScriptHost::addDiagnosticStatus(PythonScriptPtr script)
{
	lock(_statusesMutex);

	DiagnosticStatusPtr status(new diagnostic_msgs::DiagnosticStatus());

	status->name = "scriptable_analyzer/" + script->getName();
	status->hardware_id = script->getParameter("hardware_id");
	status->level = script->isValidationFailed() ? diagnostic_msgs::DiagnosticStatus::ERROR : diagnostic_msgs::DiagnosticStatus::OK;
	status->message = script->isValidationFailed() ? "Validation failed: " + script->getFailedValidation() : "Ok";;

	_diagnosticStatuses.push_back(status);
}

vector<DiagnosticStatusPtr> ScriptHost::getDiagnosticStatusesAndClear()
{
	lock(_statusesMutex);

	vector<DiagnosticStatusPtr> statuses = _diagnosticStatuses;
	_diagnosticStatuses.clear();
	return statuses;
}
